﻿/*****************************************************
 * SolutionManager version 2                         *
 * Made by Papn Kukn (papnkukn@users.sourceforge.net)*
 *****************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Xml;
using System.Diagnostics;

namespace VisualPureBasicIDE.CoreGUI.SolutionManager
{
    /// <summary>
    /// This class is for loading and saving solutions (made by Papn Kukn)
    /// </summary>
    public class SolutionManager
    {
        #region Members

        public const string ProjectExtension = "pbprj";
        public const string SolutionExtension = "pbsln";
        private FileSystemWatcher _fileSystemWatcher;
        private int _projectCount;
        private string _solutionDirectory;
        private string _solutionFilename;

        #endregion

        #region Properties

        /// <summary>
        /// Get number of projects in opened solution
        /// </summary>
        public int ProjectCount
        {
            get { return _projectCount; }
        }

        /// <summary>
        /// Get solution directory
        /// </summary>
        public string SolutionDirectory
        {
            get
            {
                if (!string.IsNullOrEmpty(_solutionDirectory) && !_solutionDirectory.EndsWith("\\"))
                {
                    _solutionDirectory += "\\";
                }
                return _solutionDirectory;
            }
        }

        /// <summary>
        /// Get solution filename
        /// </summary>
        public string SolutionFilename
        {
            get { return _solutionFilename; }
        }

        /// <summary>
        /// Enable FileSystemWatcher
        /// </summary>
        public bool EnableFileSystemWatcher
        {
            get
            {
                return _fileSystemWatcher != null && _fileSystemWatcher.EnableRaisingEvents;
            }
            set
            {
                if (_fileSystemWatcher != null)
                {
                    _fileSystemWatcher.EnableRaisingEvents = value;
                }
            }
        }

        #endregion

        #region Solution

        /// <summary>
        /// Loads a PureBasic solution from a file (using OpenFileDialog)
        /// </summary>
        public static void LoadSolution()
        {
            using (
                var fileDialog = new OpenFileDialog
                                     {
                                         Title = Translator.GetStringAndTranslate("strLoadProjectOrSolution"),
                                         Filter =
                                             Translator.GetStringAndTranslate("strProjectOrSolutionFile") +
                                             " (*.pbproj), (*.pbsln)|*.pbproj; *.pbsln|" +
                                             Translator.GetStringAndTranslate("strProjectFile") +
                                             " (*.pbproj)|*.pbproj|" +
                                             Translator.GetStringAndTranslate("strSolutionFile") + " (*.pbsln)|*.pbsln"
                                     })
            {
                if (fileDialog.ShowDialog() != DialogResult.OK) return;

                var solutionManager = new SolutionManager();
                var fileInfo = new FileInfo(fileDialog.FileName);


                WindowManager.SolutionExplorer.DockPanel = ApplicationManager.MainForm.DockPanel;

                switch (fileInfo.Extension)
                {
                    case ".pbproj":
                        {
                            WindowManager.SolutionExplorer.treeViewSolution.Nodes.Clear();

                            var root = new SolutionTreeNode("Solution \"Unknown\"", SolutionTreeNodeTypes.Solution, null);
                            var projectNode = new SolutionTreeNode();

                            solutionManager.LoadProject(ref projectNode, fileDialog.FileName);
                            root.Nodes.Add(projectNode);

                            WindowManager.SolutionExplorer.treeViewSolution.Nodes.Add(root);
                            WindowManager.SolutionExplorer.Show();
                        }
                        break;
                    case ".pbsln":
                        WindowManager.SolutionExplorer.treeViewSolution.Nodes.Clear();

                        solutionManager.LoadSolution(WindowManager.SolutionExplorer.treeViewSolution,
                                                     fileDialog.FileName);

                        WindowManager.SolutionExplorer.treeViewSolution.ActionRequest += TreeViewSolution_ActionRequest;
                        WindowManager.SolutionExplorer.Show();
                        break;
                }
            }
        }

        private static void TreeViewSolution_ActionRequest(object sender, SolutionActionEventArgs e)
        {
            if (e.Action == SolutionTreeViewActions.Open) DocumentManager.AddDocument(new Document(e.Node.Text, e.Node.Path), ApplicationManager.MainForm.DockPanel);
            if (e.Action == SolutionTreeViewActions.OpenInExplorer)
                Process.Start("explorer.exe " + e.Node.Path);
        }

        /// <summary>
        /// Loads a PureBasic solution from a file
        /// </summary>
        /// <param name="tree">The TreeView to add the solution to</param>
        /// <param name="filename">The path to the solution file</param>
        /// <returns>True if solution is loaded</returns>
        public bool LoadSolution( /*ref*/ SolutionTreeView tree, string filename)
        {
            var loaded = false;

            try
            {
                //Members

                //Create solution tree node
                tree.Nodes.Clear();
                var solutionName = string.Format("Solution \'{0}\'", Path.GetFileNameWithoutExtension(filename));
                var solutionTreeNode = new SolutionTreeNode(solutionName, SolutionTreeNodeTypes.Solution, filename);
                _solutionDirectory = Path.GetDirectoryName(filename);
                _solutionFilename = filename;

                //Create FileSystemWatcher
                if (_fileSystemWatcher == null)
                {
                    _fileSystemWatcher = new FileSystemWatcher(SolutionDirectory);
                    _fileSystemWatcher.Changed += FileSystemWatcher_Changed;
                    _fileSystemWatcher.Created += FileSystemWatcher_Created;
                    _fileSystemWatcher.Deleted += FileSystemWatcher_Deleted;
                    _fileSystemWatcher.Renamed += FileSystemWatcher_Renamed;
                    _fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
                    //Do not use NotifyFilters.FileName because it may trigger change event on node rename
                    _fileSystemWatcher.IncludeSubdirectories = true;
                    _fileSystemWatcher.EnableRaisingEvents = true;
                }
                else
                {
                    _fileSystemWatcher.Path = SolutionDirectory;
                }

                //Load solution file
                var doc = new XmlDocument();
                doc.Load(filename);
                var root = doc.DocumentElement;
                _projectCount = 0;
                if (root != null && root.LocalName == "Solution")
                {
                    loaded = true; //It is xml file and it has "Solution" for root node
                    CreateSolutionTreeNodes(ref solutionTreeNode, root);
                }

                //Update text
                solutionTreeNode.Text += string.Format(" ({0} project{1})", _projectCount, (_projectCount != 1 ? "s" : ""));
                //TODO: Localize word "project(s)"
                tree.Nodes.Add(solutionTreeNode);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                //Exception should not be sent to e-mail because there may be problems in solution file not the parsing algorithm
            }
            finally
            {
                if (!loaded)
                {
                    //TODO: Create a blank tree node with message "Cannot load solution"
                }
            }
            return loaded;
        }

        /// <summary>
        /// Save a PureBasic solution to file
        /// </summary>
        /// <param name="tree">The TreeView that has to be saved in xml</param>
        /// <param name="filename">Destination filename</param>
        /// <returns>True if file is successfully saved</returns>
        public bool SaveSolution(ref SolutionTreeView tree, string filename)
        {
            var saved = false;
            try
            {
                if (tree.Nodes.Count > 0)
                {
                    var doc = new XmlDocument();
                    doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null));
                    var root = doc.CreateElement("Solution");
                    var parentTreeNode = (SolutionTreeNode) tree.Nodes[0];
                    var destinationFolder = Path.GetDirectoryName(filename);
                    if (!string.IsNullOrEmpty(destinationFolder) && !destinationFolder.EndsWith("\\"))
                    {
                        destinationFolder += "\\";
                    }
                    CreateSolutionXmlNodes(parentTreeNode, ref root, doc, destinationFolder);
                    doc.AppendChild(root);
                    doc.Save(filename);
                    saved = true;
                }
            }
            catch
            {
            }
            return saved;
        }

        /// <summary>
        /// Create project and virtual folder XmlNodes from TreeNodes
        /// </summary>
        /// <param name="parentTreeNode">The SolutionTreeNode where projects and virtual folders are taken from</param>
        /// <param name="parentXmlNode">Generated XmlNode with project and virtual folder subnodes</param>
        /// <param name="parentXmlNode">The directory (path) where solution will be saved</param>
        /// <param name="doc">XmlDocument</param>
        /// <param name="destinationFolder">The destination folder</param>
        private void CreateSolutionXmlNodes(TreeNode parentTreeNode, ref XmlElement parentXmlNode,
                                            XmlDocument doc, string destinationFolder)
        {
            foreach (SolutionTreeNode childTreeNode in parentTreeNode.Nodes)
            {
                if (childTreeNode.Type == SolutionTreeNodeTypes.VirtualFolder)
                {
                    var folderXmlNode = doc.CreateElement("Folder");
                    folderXmlNode.SetAttribute("Name", childTreeNode.Text);
                    CreateSolutionXmlNodes(childTreeNode, ref folderXmlNode, doc, destinationFolder);
                    parentXmlNode.AppendChild(folderXmlNode);
                }
                else if (childTreeNode.Type == SolutionTreeNodeTypes.Project && !string.IsNullOrEmpty(childTreeNode.Path))
                {
                    var source = childTreeNode.Path.Replace(SolutionDirectory, string.Empty);
                    var projectXmlNode = doc.CreateElement("Project");
                    projectXmlNode.SetAttribute("Source", source);
                    parentXmlNode.AppendChild(projectXmlNode);
                    SaveProject(childTreeNode, destinationFolder + source);
                }
            }
        }

        /// <summary>
        /// Create project and virtual folder TreeNodes from XmlNodes
        /// </summary>
        /// <param name="parentTreeNode">The SolutionTreeNode to add projects and virtual folders to</param>
        /// <param name="parentXmlNode">The XmlNode that contains projects and virtual folders</param>
        private void CreateSolutionTreeNodes(ref SolutionTreeNode parentTreeNode, XmlNode parentXmlNode)
        {
            if (parentXmlNode != null)
            {
                foreach (XmlNode node in parentXmlNode.ChildNodes)
                {
                    if (node.LocalName == "Folder")
                    {
                        var folderName = (node.Attributes["Name"] != null ? node.Attributes["Name"].Value : "unknown");
                        //TODO: Localization for word "unknown"
                        var folderTreeNode = new SolutionTreeNode(folderName, SolutionTreeNodeTypes.VirtualFolder, null);
                        parentTreeNode.Nodes.Add(folderTreeNode);
                        CreateSolutionTreeNodes(ref folderTreeNode, node); //Recursive
                    }
                    else if (node.LocalName == "Project" && node.Attributes["Source"] != null)
                    {
                        var filename = node.Attributes["Source"].Value;
                        LoadProject(ref parentTreeNode, filename);
                    }
                }
            }
        }

        #endregion

        #region Project

        /// <summary>
        /// Load project from external file
        /// </summary>
        /// <param name="parentTreeNode">The SolutionTreeNode to add folders and files to</param>
        /// <param name="filename">Relative or full path to project file</param>
        /// <returns>True if project is loaded</returns>
        public bool LoadProject(ref SolutionTreeNode parentTreeNode, string filename)
        {
            var loaded = false;
            XmlDocument doc;
            XmlElement root = null;

            try
            {
                if (!File.Exists(filename))
                {
                    //Create full path from relative path
                    filename = Path.Combine(_solutionDirectory, filename);
                }

                //Try to load xml
                doc = new XmlDocument();
                doc.Load(filename);
                root = doc.DocumentElement;
            }
            catch
            {
                //Project xml cannot be opened
                var projectName = Path.GetFileNameWithoutExtension(filename) + " (unavailable)";
                parentTreeNode.Nodes.Add(new SolutionTreeNode(projectName, SolutionTreeNodeTypes.Project, filename,
                                                              TreeIcons.ProjectUnavailable));
                _projectCount++;
            }
            finally
            {
                if (root != null && root.LocalName == "Project")
                {
                    if (root.Attributes["Name"] == null)
                    {
                        //Create name from filename
                        root.SetAttribute("Name", Path.GetFileNameWithoutExtension(filename));
                    }
                    var parentXmlNode = (XmlNode) root;

                    #region Members

                    //SolutionTreeNode propertiesTreeNode = null;
                    //var pathList = new List<string>();

                    #endregion

                    #region Create project node

                    //Get project name
                    var projectName = (parentXmlNode.Attributes["Name"] != null
                                              ? parentXmlNode.Attributes["Name"].Value
                                              : "project");

                    //Create project node
                    var projectTreeNode = new SolutionTreeNode(projectName, SolutionTreeNodeTypes.Project, filename);
                    parentTreeNode.Nodes.Add(projectTreeNode);
                    _projectCount++;

                    #endregion

                    #region Create special folder nodes

                    //If there is at least one string that starts with "Properties\"
                    //if (parentXmlNode.SelectNodes(@"//Files//File[starts-with(@Source,'Properties\')]").Count > 0)
                    //{
                    //    //Create properties node
                    //    propertiesTreeNode = new SolutionTreeNode("Properties", SolutionTreeNodeTypes.Folder, null);
                    //    projectTreeNode.Nodes.Add(propertiesTreeNode);
                    //}

                    #endregion

                    #region Create file/folder nodes

                    var nodes = parentXmlNode.SelectNodes(@"//Files//File[@Source]");
                    CreateProjectTreeNodes(ref projectTreeNode, nodes, Path.GetDirectoryName(filename));

                    #endregion

                    //Project has been successfully loaded
                    loaded = true;
                }
            }

            return loaded;
        }

        /// <summary>
        /// Save project to file
        /// </summary>
        /// <param name="parentTreeNode">TreeNodes that have to be saved in xml</param>
        /// <param name="filename">Destination filename</param>
        /// <returns>True if file is saved successfully</returns>
        public bool SaveProject(SolutionTreeNode parentTreeNode, string filename)
        {
            var saved = false;
            try
            {
                var doc = new XmlDocument();
                doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null));
                var root = doc.CreateElement("Project");
                root.SetAttribute("Name", parentTreeNode.Text);
                var filesXmlNode = doc.CreateElement("Files");
                CreateProjectXmlNodes(parentTreeNode, ref filesXmlNode, doc);
                root.AppendChild(filesXmlNode);
                doc.AppendChild(root);

                var directory = Path.GetDirectoryName(filename);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                doc.Save(filename);
                saved = true;
            }
            catch
            {
            }
            return saved;
        }

        /// <summary>
        /// Generate XmlDocument for project on the fly, for internal use (like Build)
        /// </summary>
        /// <param name="parentTreeNode">TreeNodes that have to be saved in XmlDocument</param>
        /// <returns>XmlDocument for project</returns>
        public XmlDocument GetProjectXml(SolutionTreeNode parentTreeNode)
        {
            XmlDocument doc = null;
            try
            {
                doc = new XmlDocument();
                doc.AppendChild(doc.CreateXmlDeclaration("1.0", "utf-8", null));
                var root = doc.CreateElement("Project");
                root.SetAttribute("Name", parentTreeNode.Text);
                var filesXmlNode = doc.CreateElement("Files");
                CreateProjectXmlNodes(parentTreeNode, ref filesXmlNode, doc);
                root.AppendChild(filesXmlNode);
                doc.AppendChild(root);
            }
            catch
            {
            }
            return doc;
        }

        /// <summary>
        /// Create folder and file XmlNodes from TreeNodes
        /// </summary>
        /// <param name="parentTreeNode">The SolutionTreeNode where folders and files are taken from</param>
        /// <param name="parentXmlNode">Generated XmlNode with folder and file subnodes</param>
        /// <param name="doc"></param>
        private void CreateProjectXmlNodes(TreeNode parentTreeNode, ref XmlElement parentXmlNode,
                                           XmlDocument doc)
        {
            foreach (SolutionTreeNode childTreeNode in parentTreeNode.Nodes)
            {
                switch (childTreeNode.Type)
                {
                    case SolutionTreeNodeTypes.Folder:
                        CreateProjectXmlNodes(childTreeNode, ref parentXmlNode, doc);
                        break;
                    case SolutionTreeNodeTypes.File:
                        {
                            var source = childTreeNode.Path.Replace(SolutionDirectory, string.Empty);
                            var fileXmlNode = doc.CreateElement("File");
                            fileXmlNode.SetAttribute("Source", source);
                            parentXmlNode.AppendChild(fileXmlNode);
                        }
                        break;
                }
            }
        }

        /// <summary>
        /// Create folder and file TreeNodes from XmlNodes
        /// </summary>
        /// <param name="parentTreeNode">The SolutionTreeNode to add folders and files to</param>
        /// <param name="nodes">Xml nodes that contain files</param>
        /// <param name="projectDirectory">Project directory</param>
        private static void CreateProjectTreeNodes(ref SolutionTreeNode parentTreeNode, XmlNodeList nodes,
                                            string projectDirectory)
        {
            if (nodes != null && nodes.Count > 0)
            {
                var nodeList = SortProjectFileNodes(nodes);
                foreach (var node in nodeList)
                {
                    var source = (node.Attributes["Source"] != null ? node.Attributes["Source"].Value : null);
                    if (!string.IsNullOrEmpty(source))
                    {
                        var folders = source.Split('\\');
                        var count = folders.Length;
                        SolutionTreeNode previousTreeNode = null;

                        #region Create folder/file nodes

                        for (var i = 0; i < count; i++)
                        {
                            SolutionTreeNode currentTreeNode = null;
                            if (previousTreeNode == null)
                            {
                                previousTreeNode = parentTreeNode;
                            }

                            //Check if tree contains folder
                            foreach (SolutionTreeNode treeNode in previousTreeNode.Nodes)
                            {
                                if (treeNode.Text == folders[i])
                                {
                                    currentTreeNode = treeNode;
                                    break;
                                }
                            }

                            if (currentTreeNode == null)
                            {
                                //Last folder in array is actually a file
                                if (i == count - 1)
                                {
                                    //Create file node
                                    var path = Path.Combine(projectDirectory, source);
                                    currentTreeNode = new SolutionTreeNode(folders[i], SolutionTreeNodeTypes.File, path);
                                }
                                else
                                {
                                    //Create folder node
                                    var path = Path.GetDirectoryName(Path.Combine(projectDirectory, source));
                                    currentTreeNode = new SolutionTreeNode(folders[i], SolutionTreeNodeTypes.Folder,
                                                                           path);
                                }

                                if (!string.IsNullOrEmpty(currentTreeNode.Text))
                                {
                                    previousTreeNode.Nodes.Add(currentTreeNode);
                                }
                            }

                            previousTreeNode = currentTreeNode;
                        }

                        #endregion
                    }
                }
            }
        }

        /// <summary>
        /// Sort folders before files, sorting by alphabet is under development
        /// </summary>
        /// <param name="nodes">Original nodes</param>
        /// <returns>Sorted nodes</returns>
        private static List<XmlNode> SortProjectFileNodes(XmlNodeList nodes)
        {
            //TODO: Improve sorting - sort nodes by alphabet, sort files in folders
            var sortedNodes = new List<XmlNode>();

            //Add only nodes with folder
            foreach (XmlNode node in nodes)
            {
                if (node.Attributes["Source"] != null && node.Attributes["Source"].Value != null &&
                    node.Attributes["Source"].Value.Contains("\\"))
                {
                    sortedNodes.Add(node);
                }
            }

            //Add only nodes without folder (root files)
            foreach (XmlNode node in nodes)
            {
                if (node.Attributes["Source"] != null && node.Attributes["Source"].Value != null &&
                    !node.Attributes["Source"].Value.Contains("\\"))
                {
                    sortedNodes.Add(node);
                }
            }

            return sortedNodes;
        }

        #endregion

        #region Events

        private void FileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
        {
            OnFileChanged(sender, e);
        }

        private void FileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            OnFileChanged(sender, e);
        }

        private void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
        {
            OnFileChanged(sender, e);
        }

        private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            OnFileChanged(sender, e);
        }

        public event FileSystemEventHandler FileChanged;

        public void OnFileChanged(object sender, FileSystemEventArgs e)
        {
            if (FileChanged != null)
            {
                //TODO: Trigger event only for opened files?
                FileChanged(sender, e);
            }
        }

        #endregion

        #region Comments

        /**************************************************************************
         * TODO:                                                                  *
         * -add comments (done)                                                   *
         * -sorting (folders before files, ordering by alphabet)                  *
         * -extra icons (mime type, get icons)                                    *
         * -*.designer.pb should go under *.pb                                    *
         * -skip references (anyway, references should not be included in project)*
         * -special folder with difference icons                                  *
         * -check if file/folder is available on the disk (done)                  *
         * -real-time checking for file changes? (done, FileSystemWatcher)        *
         **************************************************************************/

        #endregion
    }
}